package rs.udd.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.pdfbox.pdfparser.PDFParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import rs.udd.exceptions.UddException;
import rs.udd.exceptions.UserAuthenticationCheckException;
import rs.udd.exceptions.password.NewPasswordMismatchException;
import rs.udd.model.Advertiser;
import rs.udd.model.Agent;
import rs.udd.model.Category;
import rs.udd.repository.AdvertiserRepository;
import rs.udd.repository.AgentRepository;
import rs.udd.repository.CategoryRepository;
import rs.udd.service.document.indexer.IndexManager;
import rs.udd.service.document.indexer.UDDIndexer;
import rs.udd.service.dto.agent.AgentEditDto;
import rs.udd.service.dto.agent.AgentRegisterDto;
import rs.udd.service.dto.agent.AgentUploadCvDto;
import rs.udd.service.dto.agent.SettingsDto;
import rs.udd.service.dto.user.UserPasswordEditDto;
import rs.udd.service.mappings.AgentMapping;
import rs.udd.service.util.FileUtil;
import rs.udd.service.util.FileUtil.FileTypes;
import rs.udd.service.util.SecurityUtil;

@Service
@Transactional
public class AgentServiceImpl implements AgentService {

	private static final Logger LOGGER = LogManager.getLogger(AgentServiceImpl.class);

	@Resource
	private AgentRepository agentRepository;

	@Resource
	private CategoryRepository categoryRepository;

	@Resource
	private AdvertiserRepository advertiserRepository;
	
	@Autowired
	private PasswordEncoder encoder;
	
	@Autowired
	private MailService mailService;

	@Override
	public Agent save(Agent agent) {
		return agentRepository.save(agent);
	}

	@Override
	public Agent register(AgentRegisterDto dto) throws UddException {
		try {
			LOGGER.info("Registering agent...");
			
			String encodedPassword = encoder.encode(dto.getPassword());
			dto.setPassword(encodedPassword);
			
			Agent agent = AgentMapping.register(dto);
			agent = agentRepository.save(agent);
			uploadCvInternal(agent, dto.getFolderWithCv());
			agent = agentRepository.save(agent);

			return agent;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Agent edit(AgentEditDto dto) throws UddException {

		LOGGER.info("AgentServiceImpl.edit()");

		try {
			Agent agent = agentRepository.findOne(dto.getId());

			AgentMapping.edit(dto, agent);
			agentRepository.save(agent);

			return agent;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}
	
	@Override
	public void uploadCv(AgentUploadCvDto dto) throws UddException {
		try {
			LOGGER.info("Uploading cv...");
			
			Agent loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
			Agent agentFromDb = agentRepository.findOne(loggedAgent.getId());
			
			CommonsMultipartFile cvFile = dto.getCvFile();
			if (cvFile != null) 
				uploadCvInternal(agentFromDb, cvFile);
			
			agentRepository.save(agentFromDb);
		} catch (Exception e) {
			LOGGER.error("Error", e);
			throw new UddException(e.getMessage());
		}
	}
	
	private void uploadCvInternal(Agent agent, CommonsMultipartFile cvFile) throws UddException, FileNotFoundException, IOException {
		
		if (cvFile != null) {
			String documentId = agent.getIndexedDocumentId();
			
			if (StringUtils.isNotEmpty(documentId)) {
				IndexManager.getIndexer().deleteDocument(documentId);
				FileUtil.cleanFile(agent.getFolderWithCv());
			}
			
			String fileLocation = FileUtil.handleFileUpload(cvFile, agent.getId(), FileTypes.CV);
	
			File cv = FileUtil.getFileFromPath(fileLocation);
			PDFParser parser = new PDFParser(new FileInputStream(cv));
			IndexManager.getIndexer().parseAndValidate(parser);
			
			String indexedDocumentId = UDDIndexer.generateDocumentId();
			IndexManager.getIndexer().index(cv, indexedDocumentId);
			
			agent.setFolderWithCv(fileLocation);
			agent.setIndexedDocumentId(indexedDocumentId);
		}
	}
	
	@Override
	public void deleteCv() throws UddException {
		try {
			LOGGER.info("Deleting cv...");

			Agent loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
			Agent agentFromDb = agentRepository.findOne(loggedAgent.getId());
			String cvId = agentFromDb.getIndexedDocumentId();
			boolean deleted = IndexManager.getIndexer().deleteDocument(cvId);
			if (deleted) {
				agentFromDb.setIndexedDocumentId(null);
				agentFromDb.setFolderWithCv(null);
				agentRepository.save(agentFromDb);
			}
		} catch (Exception e) {
			LOGGER.error("Error", e);
			throw new UddException(e.getMessage());
		}
	}

	@Override
	public boolean editPassword(UserPasswordEditDto dto) throws UddException {

		LOGGER.info("AgentServiceImpl.editPassword()");

		try {

			Agent checkingUser = agentRepository.findOne(dto.getId());
			SecurityUtil.checkLoggedUser(checkingUser);
			SecurityUtil.checkPasswordChange(dto);
			
			String oldRawPassword = dto.getOldPassword();
			String oldEncodedPassword = checkingUser.getPassword();
			boolean result = encoder.matches(oldRawPassword, oldEncodedPassword);
			
			if (result) {
				String newRawPassword = dto.getNewPassword();
				String newEncodedPassword = encoder.encode(newRawPassword);
				checkingUser.setPassword(newEncodedPassword);
				agentRepository.save(checkingUser);
				
				String to = checkingUser.getEmail();
				String body = "Your password has been changed. New password is: " + newRawPassword;
				mailService.sendMail(to, "Password change", body);
			}
			
			return result;
		} catch (Exception e) {

			if (e instanceof NewPasswordMismatchException) {
				throw e;
			} else if (e instanceof UserAuthenticationCheckException) {
				throw e;
			}

			throw new UddException("Udd exception", e);
		}

	}

	@Override
	public void delete(Long id) throws UddException {

		LOGGER.info("AgentServiceImpl.delete()");

		try {
			Agent agent = agentRepository.findOne(id);
			SecurityUtil.checkLoggedUser(agent);

			agentRepository.delete(id);
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public List<Agent> findAll() throws UddException {

		LOGGER.info("AgentServiceImpl.findAll()");

		try {
			return agentRepository.findAll();
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Agent findAgentByUsername(String username) throws UddException {

		LOGGER.info("AgentServiceImpl.findAgentByUsername()");

		try {
			return agentRepository.findByUsername(username);
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Agent findAgentByUsernameForView(String username) throws UddException {
		try {
			Agent agent = agentRepository.findByUsername(username);
			agent.getContactedByAdvertiser().size();
			agent.getApplicationsForAdvert().size();
			return agent;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Agent findOne(Long id) throws UddException {
		try {
			return agentRepository.findOne(id);
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public SettingsDto prepareSettingsDto() throws UddException {

		try {
			Agent sessionAgent = SecurityUtil.getLoggedUser(Agent.class);
			Agent agent = agentRepository.findOne(sessionAgent.getId());

//			List<Category> shownCategories = categoryRepository.findAllCategoriesShownByAgent(agent.getId());
//			List<Advertiser> shownAdvertisers = advertiserRepository.findAllAdvertisersShownByAgent(agent);

			List<Category> hiddenCategories = new ArrayList<>(agent.getHiddenCategories());
			List<Advertiser> hiddenAdvertisers = new ArrayList<>(agent.getHiddenAdvertisers());

			List<Category> allCategories = categoryRepository.findAll();
			for (Category hidden : hiddenCategories) {
				allCategories.remove(hidden);
			}

			List<Advertiser> allAdvertisers = advertiserRepository.findAll();
			for (Advertiser hidden : hiddenAdvertisers) {
				allAdvertisers.remove(hidden);
			}

			SettingsDto dto = new SettingsDto(allCategories, allAdvertisers, hiddenCategories, hiddenAdvertisers);
			return dto;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Category showOrHideCategory(Long id) throws UddException {
		try {
			Category category = categoryRepository.findOne(id);
			Agent sessionAgent = SecurityUtil.getLoggedUser(Agent.class);
			Agent agent = agentRepository.findOne(sessionAgent.getId());

			if (agent.getHiddenCategories().contains(category)) {
				LOGGER.info("Removing category from hiddens '" + category.getName() + "'...");
				agent.getHiddenCategories().remove(category);
				category.getHiddenByAgents().remove(agent);
			} else {
				LOGGER.info("Adding category to hiddens '" + category.getName() + "'...");
				agent.getHiddenCategories().add(category);
				category.getHiddenByAgents().add(agent);
			}
			categoryRepository.save(category);

			return category;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Advertiser showOrHideAdvertiser(Long id) throws UddException {
		try {
			Advertiser advertiser = advertiserRepository.findOne(id);
			Agent sessionAgent = SecurityUtil.getLoggedUser(Agent.class);
			Agent agent = agentRepository.findOne(sessionAgent.getId());

			if (agent.getHiddenAdvertisers().contains(advertiser)) {
				LOGGER.info("Removing advertiser from hiddens '" + advertiser.getUsername() + "'...");
				agent.getHiddenAdvertisers().remove(advertiser);
				advertiser.getHiddenByAgents().remove(agent);
			} else {
				LOGGER.info("Adding advertiser to hiddens '" + advertiser.getUsername() + "'...");
				agent.getHiddenAdvertisers().add(advertiser);
				advertiser.getHiddenByAgents().add(agent);
			}
			agentRepository.save(agent);

			return advertiser;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

}
